import os
import asyncio
import tempfile
import subprocess
import json
from typing import Optional, Dict, Any, Tuple
from pathlib import Path

from config.logging_config import ProjectLoggers
from config.video_config import VIDEO_CONFIG
from utils.request_context import RequestContext, context_logger

logger = ProjectLoggers.get_service_logger()

def get_ffmpeg_path() -> Optional[str]:
    """获取FFmpeg可执行文件路径"""
    # 常见的FFmpeg路径
    common_paths = [
        '/usr/local/bin/ffmpeg',
        '/usr/bin/ffmpeg',
        '/opt/homebrew/bin/ffmpeg',
        'ffmpeg'  # 系统PATH中
    ]
    
    for path in common_paths:
        try:
            # 测试路径是否有效
            result = subprocess.run([path, '-version'], 
                                   capture_output=True, 
                                   text=True, 
                                   timeout=5)
            if result.returncode == 0:
                logger.info(f"找到FFmpeg: {path}")
                return path
        except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
            continue
    
    logger.warning("未找到FFmpeg")
    return None

def get_ffprobe_path() -> Optional[str]:
    """获取FFprobe可执行文件路径"""
    # 常见的FFprobe路径
    common_paths = [
        '/usr/local/bin/ffprobe',
        '/usr/bin/ffprobe',
        '/opt/homebrew/bin/ffprobe',
        'ffprobe'  # 系统PATH中
    ]
    
    for path in common_paths:
        try:
            # 测试路径是否有效
            result = subprocess.run([path, '-version'], 
                                   capture_output=True, 
                                   text=True, 
                                   timeout=5)
            if result.returncode == 0:
                logger.info(f"找到FFprobe: {path}")
                return path
        except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
            continue
    
    logger.warning("未找到FFprobe")
    return None

class FFmpegHelper:
    """FFmpeg工具类，专门用于YouTube视频处理"""
    
    @staticmethod
    def check_ffmpeg_available() -> bool:
        """检查FFmpeg是否可用"""
        ffmpeg_path = get_ffmpeg_path()
        ffprobe_path = get_ffprobe_path()
        return ffmpeg_path is not None and ffprobe_path is not None
    
    @staticmethod
    async def get_video_info(video_path: str) -> Optional[Dict[str, Any]]:
        """获取视频文件信息"""
        try:
            ffprobe_path = get_ffprobe_path()
            if not ffprobe_path:
                logger.error("FFprobe不可用")
                return None
            
            cmd = [
                ffprobe_path,
                '-v', 'quiet',
                '-print_format', 'json',
                '-show_format',
                '-show_streams',
                video_path
            ]
            
            # 异步执行ffprobe
            loop = asyncio.get_event_loop()
            process = await loop.run_in_executor(
                None,
                lambda: subprocess.run(cmd, capture_output=True, text=True, timeout=30)
            )
            
            if process.returncode != 0:
                logger.error(f"FFprobe执行失败: {process.stderr}")
                return None
            
            probe_data = json.loads(process.stdout)
            
            # 解析基本信息
            format_info = probe_data.get('format', {})
            streams = probe_data.get('streams', [])
            
            video_info = {
                'duration': float(format_info.get('duration', 0)),
                'size': int(format_info.get('size', 0)),
                'format': format_info.get('format_name', ''),
                'bit_rate': int(format_info.get('bit_rate', 0)),
                'video_streams': [],
                'audio_streams': []
            }
            
            # 解析流信息
            for stream in streams:
                if stream['codec_type'] == 'video':
                    video_stream = {
                        'codec_name': stream.get('codec_name', ''),
                        'codec_long_name': stream.get('codec_long_name', ''),
                        'width': stream.get('width', 0),
                        'height': stream.get('height', 0),
                        'bit_rate': int(stream.get('bit_rate', 0)),
                        'duration': float(stream.get('duration', 0)),
                        'fps': FFmpegHelper._parse_frame_rate(stream.get('r_frame_rate', '0/1'))
                    }
                    video_info['video_streams'].append(video_stream)
                
                elif stream['codec_type'] == 'audio':
                    audio_stream = {
                        'codec_name': stream.get('codec_name', ''),
                        'codec_long_name': stream.get('codec_long_name', ''),
                        'sample_rate': int(stream.get('sample_rate', 0)),
                        'channels': int(stream.get('channels', 0)),
                        'bit_rate': int(stream.get('bit_rate', 0)),
                        'duration': float(stream.get('duration', 0))
                    }
                    video_info['audio_streams'].append(audio_stream)
            
            logger.info(f"获取视频信息成功: {video_path}, 时长: {video_info['duration']}秒")
            return video_info
            
        except Exception as e:
            logger.error(f"获取视频信息失败 {video_path}: {str(e)}")
            return None
    
    @staticmethod
    def _parse_frame_rate(frame_rate_str: str) -> float:
        """解析帧率字符串"""
        try:
            if '/' in frame_rate_str:
                num, den = frame_rate_str.split('/')
                return float(num) / float(den) if float(den) != 0 else 0
            return float(frame_rate_str)
        except:
            return 0
    
    @staticmethod
    async def merge_video_audio_professional(
        video_path: str,
        audio_path: Optional[str],
        output_path: str,
        check_sync: bool = True
    ) -> Dict[str, Any]:
        """
        专业级视频音频合成，严格按照技术规范
        
        Args:
            video_path: 视频文件路径
            audio_path: 音频文件路径（可选）
            output_path: 输出文件路径
            check_sync: 是否检查音视频同步
        
        Returns:
            包含成功状态和详细信息的字典
        """
        try:
            ffmpeg_path = get_ffmpeg_path()
            if not ffmpeg_path:
                return {
                    "success": False,
                    "error": "FFmpeg不可用"
                }
            
            # 检查输入文件
            if not os.path.exists(video_path):
                return {
                    "success": False,
                    "error": f"视频文件不存在: {video_path}"
                }
            
            if audio_path and not os.path.exists(audio_path):
                return {
                    "success": False,
                    "error": f"音频文件不存在: {audio_path}"
                }
            
            # 如果需要检查同步，先获取文件信息
            sync_warning = None
            if check_sync and audio_path:
                video_info = await FFmpegHelper.get_video_info(video_path)
                audio_info = await FFmpegHelper.get_video_info(audio_path)
                
                if video_info and audio_info:
                    video_duration = video_info.get('duration', 0)
                    audio_duration = audio_info.get('duration', 0)
                    duration_diff = abs(video_duration - audio_duration)
                    
                    if duration_diff > 3:
                        sync_warning = f"警告: 视频时长({video_duration:.1f}s)与音频时长({audio_duration:.1f}s)差异过大({duration_diff:.1f}s)"
                        logger.warning(sync_warning)
            
            # 构建专业FFmpeg命令
            cmd = [ffmpeg_path]
            
            # 输入文件
            cmd.extend(['-i', video_path])
            if audio_path:
                cmd.extend(['-i', audio_path])
            
            # 核心参数 - 严格按照用户要求
            if audio_path:
                # 有音频的情况：直接流复制，不重编码
                cmd.extend([
                    '-c:v', 'copy',      # 视频流直接复制
                    '-c:a', 'copy',      # 音频流直接复制（如果兼容）
                    '-map', '0:v:0',     # 映射第一个视频流
                    '-map', '1:a:0',     # 映射第一个音频流
                ])
            else:
                # 仅视频的情况
                cmd.extend([
                    '-c:v', 'copy',      # 视频流直接复制
                    '-an'                # 无音频
                ])
            
            # 优化参数
            cmd.extend([
                '-movflags', '+faststart',  # 优化网络播放
                '-avoid_negative_ts', 'make_zero',  # 处理时间戳
                '-fflags', '+genpts',       # 生成PTS
            ])
            
            # 输出设置
            cmd.extend([
                '-y',                # 覆盖输出文件
                output_path
            ])
            
            logger.info(f"🔧 执行专业FFmpeg合成命令: {' '.join(cmd[:10])}...")
            
            # 执行FFmpeg命令
            process = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            
            stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=600)  # 10分钟超时
            
            if process.returncode == 0:
                # 验证输出文件
                if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
                    file_size = os.path.getsize(output_path)
                    
                    # 获取输出文件信息用于验证
                    output_info = await FFmpegHelper.get_video_info(output_path)
                    
                    result = {
                        "success": True,
                        "output_path": output_path,
                        "file_size": file_size,
                        "file_size_mb": round(file_size / 1024 / 1024, 2),
                        "method": "专业级流复制合成",
                        "has_audio": bool(audio_path),
                        "output_info": output_info
                    }
                    
                    if sync_warning:
                        result["sync_warning"] = sync_warning
                    
                    logger.info(f"✅ 专业FFmpeg合成成功: {result['file_size_mb']}MB")
                    return result
                else:
                    return {
                        "success": False,
                        "error": "合成的文件无效或为空"
                    }
            else:
                # 如果直接复制失败，尝试重新编码
                logger.warning("流复制失败，尝试重新编码...")
                stderr_text = stderr.decode('utf-8', errors='ignore')
                
                return await FFmpegHelper._merge_with_reencoding(
                    video_path, audio_path, output_path, stderr_text
                )
                
        except asyncio.TimeoutError:
            return {
                "success": False,
                "error": "FFmpeg处理超时（超过10分钟）"
            }
        except Exception as e:
            logger.error(f"FFmpeg专业合成异常: {str(e)}")
            return {
                "success": False,
                "error": f"合成异常: {str(e)}"
            }
    
    @staticmethod
    async def _merge_with_reencoding(
        video_path: str,
        audio_path: Optional[str],
        output_path: str,
        original_error: str
    ) -> Dict[str, Any]:
        """重新编码合成（当直接复制失败时的备选方案）"""
        try:
            ffmpeg_path = get_ffmpeg_path()
            
            # 构建重新编码命令
            cmd = [ffmpeg_path]
            
            # 输入文件
            cmd.extend(['-i', video_path])
            if audio_path:
                cmd.extend(['-i', audio_path])
            
            # 重新编码参数 - 保证兼容性
            if audio_path:
                cmd.extend([
                    '-c:v', 'libx264',       # H.264编码
                    '-preset', 'medium',     # 编码预设
                    '-crf', '18',           # 高质量
                    '-c:a', 'aac',          # AAC音频编码
                    '-b:a', '128k',         # 音频码率
                    '-map', '0:v:0',        # 映射视频流
                    '-map', '1:a:0',        # 映射音频流
                ])
            else:
                cmd.extend([
                    '-c:v', 'libx264',       # H.264编码
                    '-preset', 'medium',     # 编码预设
                    '-crf', '18',           # 高质量
                    '-an'                    # 无音频
                ])
            
            # 优化参数
            cmd.extend([
                '-movflags', '+faststart',
                '-avoid_negative_ts', 'make_zero',
                '-y',
                output_path
            ])
            
            logger.info("🔧 执行重新编码合成...")
            
            # 执行重新编码
            process = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            
            stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=1200)  # 20分钟超时
            
            if process.returncode == 0:
                if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
                    file_size = os.path.getsize(output_path)
                    
                    return {
                        "success": True,
                        "output_path": output_path,
                        "file_size": file_size,
                        "file_size_mb": round(file_size / 1024 / 1024, 2),
                        "method": "重新编码合成（备选方案）",
                        "has_audio": bool(audio_path),
                        "original_error": original_error
                    }
                else:
                    return {
                        "success": False,
                        "error": "重新编码后的文件无效"
                    }
            else:
                stderr_text = stderr.decode('utf-8', errors='ignore')
                return {
                    "success": False,
                    "error": f"重新编码也失败: {stderr_text}"
                }
                
        except Exception as e:
            return {
                "success": False,
                "error": f"重新编码异常: {str(e)}"
            }
    
    @staticmethod
    def validate_video_format(filename: str) -> bool:
        """验证视频格式"""
        valid_extensions = ['.mp4', '.avi', '.mov', '.mkv', '.webm', '.m4v']
        return any(filename.lower().endswith(ext) for ext in valid_extensions)
    
    @staticmethod
    def validate_audio_format(filename: str) -> bool:
        """验证音频格式"""
        valid_extensions = ['.mp3', '.aac', '.m4a', '.wav', '.ogg', '.flac', '.webm']
        return any(filename.lower().endswith(ext) for ext in valid_extensions)
    
    @staticmethod
    def get_temp_filename(prefix: str = "ffmpeg", suffix: str = ".mp4") -> str:
        """生成临时文件名"""
        temp_dir = tempfile.gettempdir()
        import uuid
        unique_id = str(uuid.uuid4())[:8]
        return os.path.join(temp_dir, f"{prefix}_{unique_id}{suffix}")
    
    @staticmethod
    def cleanup_temp_file(file_path: str) -> bool:
        """清理临时文件"""
        try:
            if os.path.exists(file_path):
                os.remove(file_path)
                logger.info(f"清理临时文件: {file_path}")
                return True
            return False
        except Exception as e:
            logger.warning(f"清理临时文件失败 {file_path}: {str(e)}")
            return False
    
    @staticmethod
    async def extract_audio(
        video_path: str,
        output_path: str,
        audio_format: str = None,
        audio_quality: str = None,
        progress_callback: Optional[callable] = None
    ) -> bool:
        """从视频中提取音频"""
        try:
            if not audio_format:
                audio_format = VIDEO_CONFIG['default_output_format']
            
            if not audio_quality:
                audio_quality = 'high'  # 默认高质量
            
            # 检查输入文件是否存在
            if not os.path.exists(video_path):
                context_logger.error(f"输入视频文件不存在: {video_path}")
                return False
            
            # 检查输入文件大小
            file_size = os.path.getsize(video_path)
            if file_size == 0:
                context_logger.error(f"输入视频文件为空: {video_path}")
                return False
            
            context_logger.info(f"输入文件检查通过: {video_path}, 大小: {file_size} bytes")
            
            # 首先获取视频信息，检查是否包含音频流
            context_logger.info("检查视频文件是否包含音频流...")
            video_info = await FFmpegHelper.get_video_info(video_path)
            if not video_info:
                context_logger.error(f"无法获取视频文件信息: {video_path}")
                return False
            
            # 检查是否有音频流
            has_audio = len(video_info.get('audio_streams', [])) > 0
            if not has_audio:
                context_logger.error(f"视频文件不包含音频轨道，无法提取音频: {video_path}")
                # 记录视频流信息
                stream_info_list = []
                for vs in video_info.get('video_streams', []):
                    stream_info_list.append(f"video({vs.get('codec_name')})")
                for as_ in video_info.get('audio_streams', []):
                    stream_info_list.append(f"audio({as_.get('codec_name')})")
                context_logger.error(f"视频流信息: {stream_info_list}")
                return False
            
            context_logger.info(f"视频文件包含音频流，可以进行音频提取")
            
            # 确保输出目录存在
            output_dir = os.path.dirname(output_path)
            if output_dir and not os.path.exists(output_dir):
                os.makedirs(output_dir, exist_ok=True)
                context_logger.info(f"创建输出目录: {output_dir}")
            
            # 根据格式和质量获取音频参数
            audio_args = FFmpegHelper._get_audio_args(audio_format, audio_quality)
            
            # 构建FFmpeg命令
            ffmpeg_path = get_ffmpeg_path()
            if not ffmpeg_path:
                context_logger.error("FFmpeg不可用")
                return False
            
            cmd = [ffmpeg_path, '-i', video_path]
            
            # 添加音频参数
            for key, value in audio_args.items():
                if key == 'acodec':
                    cmd.extend(['-acodec', value])
                elif key == 'ar':
                    cmd.extend(['-ar', str(value)])
                elif key == 'ab':
                    cmd.extend(['-ab', value])
                elif key == 'q:a':
                    cmd.extend(['-q:a', value])
                elif key == 'compression_level':
                    cmd.extend(['-compression_level', str(value)])
            
            # 添加输出文件
            cmd.extend(['-y', output_path])
            
            context_logger.info(f"开始提取音频: {video_path} -> {output_path}")
            context_logger.info(f"FFmpeg 命令参数: {audio_args}")
            
            # 异步执行FFmpeg命令
            process = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            
            stdout, stderr = await process.communicate()
            
            if process.returncode == 0:
                # 检查输出文件是否存在
                if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
                    output_size = os.path.getsize(output_path)
                    context_logger.info(f"音频提取成功: {output_path}, 大小: {output_size} bytes")
                    return True
                else:
                    context_logger.error(f"音频提取失败，输出文件不存在或为空: {output_path}")
                    return False
            else:
                stderr_text = stderr.decode('utf-8', errors='ignore')
                context_logger.error(f"FFmpeg执行失败: {stderr_text}")
                return False
                
        except Exception as e:
            context_logger.error(f"音频提取失败 {video_path}: {str(e)}")
            context_logger.error(f"错误类型: {type(e).__name__}")
            import traceback
            context_logger.error(f"完整错误堆栈: {traceback.format_exc()}")
            return False
    
    @staticmethod
    def _get_audio_codec(audio_format: str) -> str:
        """根据音频格式获取对应的编解码器"""
        codec_map = {
            'mp3': 'libmp3lame',
            'aac': 'aac',
            'wav': 'pcm_s16le',
            'ogg': 'libvorbis',
            'flac': 'flac'
        }
        return codec_map.get(audio_format.lower(), 'libmp3lame')
    
    @staticmethod
    def _get_audio_args(audio_format: str, audio_quality: str) -> dict:
        """根据音频格式和质量获取FFmpeg参数"""
        format_lower = audio_format.lower()
        quality_lower = audio_quality.lower()
        
        # 获取基础编解码器
        codec = FFmpegHelper._get_audio_codec(format_lower)
        
        # 基础参数
        args = {
            'acodec': codec,
            'ar': VIDEO_CONFIG['audio_sample_rate']  # 采样率
        }
        
        # 根据格式和质量设置具体参数
        if format_lower == 'mp3':
            if quality_lower == 'lossless':
                args['ab'] = '320k'  # MP3最高320k
                args['q:a'] = '0'    # 最高质量
            else:
                bitrate = VIDEO_CONFIG['format_quality_map']['mp3'][quality_lower]
                args['ab'] = bitrate
                
        elif format_lower == 'aac':
            if quality_lower == 'lossless':
                args['ab'] = '256k'  # AAC最高256k
                args['q:a'] = '1'    # 高质量
            else:
                bitrate = VIDEO_CONFIG['format_quality_map']['aac'][quality_lower]
                args['ab'] = bitrate
                
        elif format_lower == 'wav':
            # WAV格式根据质量选择不同的PCM编码
            codec_quality = VIDEO_CONFIG['format_quality_map']['wav'][quality_lower]
            args['acodec'] = codec_quality
            # WAV不需要比特率参数
            
        elif format_lower == 'flac':
            # FLAC使用压缩级别
            compression_level = VIDEO_CONFIG['format_quality_map']['flac'][quality_lower]
            args['compression_level'] = compression_level
            # FLAC不需要比特率参数
            
        elif format_lower == 'ogg':
            if quality_lower == 'lossless':
                args['ab'] = '320k'
                args['q:a'] = '10'   # OGG最高质量
            else:
                bitrate = VIDEO_CONFIG['format_quality_map']['ogg'][quality_lower]
                args['ab'] = bitrate
        
        context_logger.info(f"音频参数配置: 格式={format_lower}, 质量={quality_lower}, 参数={args}")
        return args
    
    @staticmethod
    async def convert_audio_format(
        input_path: str,
        output_path: str,
        target_format: str,
        quality: str = None
    ) -> bool:
        """转换音频格式"""
        try:
            if not quality:
                quality = 'high'  # 默认高质量
            
            # 检查输入文件是否存在
            if not os.path.exists(input_path):
                logger.error(f"输入音频文件不存在: {input_path}")
                return False
            
            # 检查输入文件大小
            file_size = os.path.getsize(input_path)
            if file_size == 0:
                logger.error(f"输入音频文件为空: {input_path}")
                return False
            
            logger.info(f"输入文件检查通过: {input_path}, 大小: {file_size} bytes")
            
            # 确保输出目录存在
            output_dir = os.path.dirname(output_path)
            if output_dir and not os.path.exists(output_dir):
                os.makedirs(output_dir, exist_ok=True)
                logger.info(f"创建输出目录: {output_dir}")
            
            # 根据格式和质量获取音频参数
            audio_args = FFmpegHelper._get_audio_args(target_format, quality)
            
            # 构建FFmpeg命令
            ffmpeg_path = get_ffmpeg_path()
            if not ffmpeg_path:
                logger.error("FFmpeg不可用")
                return False
            
            cmd = [ffmpeg_path, '-i', input_path]
            
            # 添加音频参数
            for key, value in audio_args.items():
                if key == 'acodec':
                    cmd.extend(['-acodec', value])
                elif key == 'ar':
                    cmd.extend(['-ar', str(value)])
                elif key == 'ab':
                    cmd.extend(['-ab', value])
                elif key == 'q:a':
                    cmd.extend(['-q:a', value])
                elif key == 'compression_level':
                    cmd.extend(['-compression_level', str(value)])
            
            # 添加输出文件
            cmd.extend(['-y', output_path])
            
            logger.info(f"开始转换音频格式: {input_path} -> {output_path}")
            logger.info(f"FFmpeg 命令参数: {audio_args}")
            
            # 异步执行FFmpeg命令
            process = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            
            stdout, stderr = await process.communicate()
            
            if process.returncode == 0:
                if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
                    output_size = os.path.getsize(output_path)
                    logger.info(f"音频格式转换成功: {input_path} -> {output_path}, 大小: {output_size} bytes")
                    return True
                else:
                    logger.error(f"音频格式转换失败，输出文件不存在或为空: {output_path}")
                    return False
            else:
                stderr_text = stderr.decode('utf-8', errors='ignore')
                logger.error(f"FFmpeg执行失败: {stderr_text}")
                return False
                
        except Exception as e:
            logger.error(f"音频格式转换失败 {input_path}: {str(e)}")
            logger.error(f"错误类型: {type(e).__name__}")
            import traceback
            logger.error(f"完整错误堆栈: {traceback.format_exc()}")
            return False 